#include "event/znx_resolver.h"
#include "comm/znx_buf.h"

znx_str_t query = {
    .data = (u_char *)"www.aaa.com",
    .len = sizeof("www.aaa.com") - 1
};


znx_str_t query_tt = {
    .data = (u_char *)"www.toutiao.com",
    .len = sizeof("www.toutiao.com") - 1
};

znx_str_t query_dy = {
    .data = (u_char *)"www.douyin.com",
    .len = sizeof("www.douyin.com") - 1
};


static znx_resolver_t *g_resolver;

void test_build_name_query()
{
    znx_resolver_task_t *task =
        znx_resolver_task_create(NULL, NULL, "", ZNX_RESOLVE_A,
            &query, ZNX_TRUE, ZNX_FALSE, 100, 0);

    task->ctx = znx_resolver_task_ctx_create();

    if (!znx_testing_resolver_build_name_query(task)) {
        ZNX_DEF_LOG_ERROR(NULL, "build name query failed");
    }

    u_char *p;
    p = task->ctx->query;

    ZNX_DEF_LOG_DEBUG(NULL, "ident_hi: %d, ident_lo: %d", *p, *(p + 1));
    // query->flags_hi = 1; query->flags_lo = 0;
    if (*(p + 2) != 1 || *(p + 3) != 0) {
        ZNX_DEF_LOG_FATAL(NULL, "expect 1:3, but got %d:%d", *(p + 2), *(p + 3));
    }
    // /* one question */
    // query->nqs_hi = 0; query->nqs_lo = 1;
    // query->nan_hi = 0; query->nan_lo = 0;
    // query->nns_hi = 0; query->nns_lo = 0;
    // query->nar_hi = 0; query->nar_lo = 0;
    if (*(p + 4) != 0 || *(p + 5) != 1 ||
        *(p + 6) != 0 || *(p + 7) != 0 ||
        *(p + 8) != 0 || *(p + 9) != 0 ||
        *(p + 10) != 0 || *(p + 11) != 0) {
        ZNX_DEF_LOG_FATAL(NULL, "expect 0:1:0:0:0:0:0:0, but got %d:%d:%d:%d:%d:%d:%d:%d",
            *(p + 4), *(p + 5), *(p + 6), *(p + 7), *(p + 8),
            *(p + 9), *(p + 10), *(p + 11));
    }

    if (znx_strncmp((char *)p + 12, "\3www\3aaa\3com", 12) != 0) {
        ZNX_DEF_LOG_FATAL(NULL, "expect [\\3www\\3aaa\\3com], but got other");
    }

    if (*(p + 25) != 0 || *(p + 26) != ZNX_RESOLVE_A ||
        *(p + 27) != 0 || *(p+28) != 1) {
        ZNX_DEF_LOG_FATAL(NULL, "expect 0:1:0:1, but got %d:%d:%d:%d",
            *(p + 25), *(p + 26), *(p + 27), *(p + 28));
    }

    znx_resolver_task_destroy(task);
}


void test_resolver_decode_name()
{
    size_t pos = 0;
    u_char buf[13] = "\3www\3aaa\3com\0";
    int ret = znx_testing_resolver_decode_name(buf, 13, &pos);
    if (ret != 0) {
        ZNX_DEF_LOG_FATAL(NULL, "expect 0, but got %d", ret);
    }

    ZNX_DEF_LOG_DEBUG(NULL, "name: %s, pos: %d", t_buf, pos);
}


void znx_test_resolver_done_1(znx_resolver_task_t *task)
{
    znx_resolver_result_item_t  *item, *items;
    znx_resolver_result_t       *result = task->result;

    if (result->eno != 0) {
        ZNX_DEF_LOG_FATAL(NULL, "dns resolver failed, eno: %d", result->eno);
        znx_resolver_task_destroy(task);
        return;
    }

    ZNX_DEF_LOG_DEBUG(NULL, "dns resolver success");
    ZNX_DEF_LOG_DEBUG(NULL, "query_type: %d, ttl: %d", task->tcp_query, result->ttl);

    items = result->arr->elts;
    for (size_t i = 0; i < result->arr->nelts; i++) {
        item = items + i;
        ZNX_DEF_LOG_DEBUG(NULL, "type: %d, name: %v, data: %v", item->type, &item->name, &item->data);
    }

    znx_resolver_task_destroy(task);
}


void znx_test_resolver_done(znx_resolver_task_t *task)
{
    znx_resolver_result_item_t  *item, *items;
    znx_resolver_result_t       *result = task->result;
    if (result->eno != 0) {
        ZNX_DEF_LOG_FATAL(NULL, "dns resolver failed, eno: %d", result->eno);
        znx_resolver_task_destroy(task);
        return;
    }

    ZNX_DEF_LOG_DEBUG(NULL, "dns resolver success");
    ZNX_DEF_LOG_DEBUG(NULL, "query_type: %d, ttl: %d",
        task->tcp_query, result->ttl);

    items = result->arr->elts;
    for (size_t i = 0; i < result->arr->nelts; i++) {
        item = items + i;
        ZNX_DEF_LOG_DEBUG(NULL, "type: %d, name: %v, data: %v", item->type, &item->name, &item->data);
    }

    znx_bool_t tcp_query = task->tcp_query ? ZNX_TRUE : ZNX_FALSE;

    // create new task. testing resolver cache.
    znx_resolver_task_t *task_1 = znx_resolver_task_create(NULL,
        znx_test_resolver_done_1,
        "test", task->qtype,
        &task->query, tcp_query, ZNX_FALSE, 100, 0);

    znx_resolver_task_destroy(task);

    znx_resolver_query(g_resolver, task_1);
}


void znx_test_resolver_handler(znx_dispatch_event_t *dispatch_event)
{
    znx_bool_t tcp_query = ZNX_FALSE;
    if (dispatch_event->data != NULL) {
        tcp_query = ZNX_TRUE;
    }

    znx_thread_free(dispatch_event);

    znx_resolver_task_t *task = znx_resolver_task_create(NULL,
        znx_test_resolver_done,
        "test", ZNX_RESOLVE_A,
        &query_tt, tcp_query, ZNX_FALSE, 500, 0);

    znx_resolver_query(g_resolver, task);

    task = znx_resolver_task_create(NULL,
        znx_test_resolver_done,
        "test", ZNX_RESOLVE_A,
        &query_dy, tcp_query, ZNX_FALSE, 500, 0);

    znx_resolver_query(g_resolver, task);
}


void test_dns_resolver(znx_bool_t tcp_query)
{
    znx_dispatch_event_t *dispatch_event =
        znx_thread_calloc(sizeof(znx_dispatch_event_t));
    dispatch_event->handler = znx_test_resolver_handler;
    if (tcp_query) {
        dispatch_event->data = (void *)(intptr_t)1;
    }
    znx_worker_processor_post_event(dispatch_event);
}


void znx_test_resolver_aaaa_handler(znx_dispatch_event_t *dispatch_event)
{
    znx_bool_t tcp_query = ZNX_FALSE;
    if (dispatch_event->data != NULL) {
        tcp_query = ZNX_TRUE;
    }

    znx_thread_free(dispatch_event);

    znx_resolver_task_t *task = znx_resolver_task_create(NULL,
        znx_test_resolver_done,
        "test", ZNX_RESOLVE_AAAA,
        &query_tt, tcp_query, ZNX_FALSE, 500, 0);

    znx_resolver_query(g_resolver, task);

    task = znx_resolver_task_create(NULL,
        znx_test_resolver_done,
        "test", ZNX_RESOLVE_AAAA,
        &query_dy, tcp_query, ZNX_FALSE, 500, 0);

    znx_resolver_query(g_resolver, task);
}


void test_dns_resolver_aaaa(znx_bool_t tcp_query)
{
    znx_dispatch_event_t *dispatch_event =
        znx_thread_calloc(sizeof(znx_dispatch_event_t));
    dispatch_event->handler = znx_test_resolver_aaaa_handler;
    if (tcp_query) {
        dispatch_event->data = (void *)(intptr_t)1;
    }
    znx_worker_processor_post_event(dispatch_event);
}


void znx_test_dual_resolver_done(znx_dual_resolver_task_t *task)
{
    znx_resolver_result_item_t      *item, *items;
    znx_resolver_result_t           *result;

    if (task->v4_task == NULL || task->v4_task->result->eno != 0) {
        ZNX_DEF_LOG_FATAL(NULL, "dual resolver v4 failed, eno: %d", task->v4_task->result->eno);
        znx_dual_resolver_task_destroy(task);
        return;
    }

    if (task->v6_task == NULL || task->v6_task->result->eno != 0) {
        ZNX_DEF_LOG_FATAL(NULL, "dual resolver v6 failed, eno: %d", task->v6_task->result->eno);
        znx_dual_resolver_task_destroy(task);
        return;
    }

    ZNX_DEF_LOG_DEBUG(NULL, "dual dns resolver success");

    result = task->v4_task->result;
    items = result->arr->elts;
    for (size_t i = 0; i < result->arr->nelts; i++) {
        item = items + i;
        ZNX_DEF_LOG_DEBUG(NULL, "type: %d, name: %v, data: %v", item->type, &item->name, &item->data);
    }

    result = task->v6_task->result;
    items = result->arr->elts;
    for (size_t i = 0; i < result->arr->nelts; i++) {
        item = items + i;
        ZNX_DEF_LOG_DEBUG(NULL, "type: %d, name: %v, data: %v", item->type, &item->name, &item->data);
    }

    znx_dual_resolver_task_destroy(task);
}


void znx_test_dual_resolver_handler(znx_dispatch_event_t *dispatch_event)
{
    znx_thread_free(dispatch_event);

    znx_dual_resolver_task_t *task;
    task = znx_dual_resolver_task_create(
        ZNX_DUAL_RESOLVE_ALL,
        NULL,
        znx_test_dual_resolver_done,
        "test", &query_tt, ZNX_FALSE, ZNX_FALSE, 500, 0);

    znx_dual_resolver_query(g_resolver, g_resolver, task);
}


void test_dual_resolver()
{
    znx_dispatch_event_t *dispatch_event =
        znx_thread_calloc(sizeof(znx_dispatch_event_t));
    dispatch_event->handler = znx_test_dual_resolver_handler;
    znx_worker_processor_post_event(dispatch_event);
}


int main()
{
    znx_proc_init();

    test_build_name_query();
    test_resolver_decode_name();

    g_resolver = znx_resolver_create(NULL, 100);
    if (g_resolver == NULL) {
        ZNX_DEF_LOG_FATAL(NULL, "create resolver failed");
        return 1;
    }

    ZNX_DEF_LOG_DEBUG(NULL, "names: %d", g_resolver->names->nelts);
    znx_resolver_nameserver_t *nameserver;
    znx_resolver_nameserver_t *nameservers = g_resolver->names->elts;
    for (size_t i = 0; i < g_resolver->names->nelts; i++) {
        nameserver = nameservers + i;
        ZNX_DEF_LOG_DEBUG(NULL, "address: %s, bind_address: %s",
            nameserver->address_text,
            nameserver->bind_address_text);
    }

    znx_worker_processor_start(2, 0, 0);

    test_dns_resolver(ZNX_FALSE);
    test_dns_resolver(ZNX_FALSE);
    test_dns_resolver(ZNX_TRUE);
    test_dns_resolver(ZNX_TRUE);

    test_dns_resolver_aaaa(ZNX_FALSE);
    test_dns_resolver_aaaa(ZNX_FALSE);
    test_dns_resolver_aaaa(ZNX_TRUE);
    test_dns_resolver_aaaa(ZNX_TRUE);

    test_dual_resolver();

    znx_proc_shut_down();

    znx_resolver_destroy(g_resolver);

    return 0;
}